#!/bin/bash

# vim:expandtab:shiftwidth=4:softtabstop=4:tabstop=4:

#
# lc_common - This file contains common variables and functions to be used by
#             Lustre cluster config scripts.
#
################################################################################

#****************************** Common Variables ******************************#
export PATH=$PATH:/sbin:/usr/sbin

# Remote command
export REMOTE=${REMOTE:-"ssh -x -q"}
#export REMOTE=${REMOTE:-"pdsh -S -R ssh -w"}

# Lustre utilities
export MKFS=${MKFS:-"mkfs.lustre"}
export TUNEFS=${TUNEFS:-"tunefs.lustre"}
export LCTL=${LCTL:-"lctl"}

# Software RAID command
export MDADM=${MDADM:-"mdadm"}

# Some scripts to be called
export MODULE_CONFIG=${MODULE_CONFIG:-"lc_modprobe"}
export VERIFY_CLUSTER_NET=${VERIFY_CLUSTER_NET:-"lc_net"}
export GEN_HB_CONFIG=${GEN_HB_CONFIG:-"lc_hb"}
export GEN_CLUMGR_CONFIG=${GEN_CLUMGR_CONFIG:-"lc_cluman"}
export SCRIPT_VERIFY_SRVIP=${SCRIPT_VERIFY_SRVIP:-"lc_servip"}
export SCRIPT_GEN_MONCF=${SCRIPT_GEN_MONCF:-"lc_mon"}
export SCRIPT_CONFIG_MD=${SCRIPT_CONFIG_MD:-"lc_md"}
export SCRIPT_CONFIG_LVM=${SCRIPT_CONFIG_LVM:-"lc_lvm"}

# Variables of HA software
HBVER_HBV1="hbv1"                   # Heartbeat version 1
HBVER_HBV2="hbv2"                   # Heartbeat version 2
HATYPE_CLUMGR="cluman"              # Cluster Manager

# Configuration directories and files
HA_DIR=${HA_DIR:-"/etc/ha.d"}		# Heartbeat configuration directory
MON_DIR=${MON_DIR:-"/etc/mon"}		# mon configuration directory
CIB_DIR=${CIB_DIR:-"/var/lib/heartbeat/crm"}	# cib.xml directory

HA_CF=${HA_DIR}/ha.cf               # ha.cf file
HA_RES=${HA_DIR}/haresources        # haresources file
HA_CIB=${CIB_DIR}/cib.xml

CLUMAN_DIR="/etc"			        # CluManager configuration directory
CLUMAN_CONFIG=${CLUMAN_DIR}/cluster.xml

CLUMAN_TOOLS_PATH=${CLUMAN_TOOLS_PATH:-"/usr/sbin"}	# CluManager tools
CONFIG_CMD=${CONFIG_CMD:-"${CLUMAN_TOOLS_PATH}/redhat-config-cluster-cmd"}

HB_TMP_DIR="/tmp/heartbeat"         # Temporary directory
CLUMGR_TMP_DIR="/tmp/clumanager"
TMP_DIRS="${HB_TMP_DIR} ${CLUMGR_TMP_DIR}"

FS_TYPE=${FS_TYPE:-"lustre"}        # Lustre filesystem type
FILE_SUFFIX=${FILE_SUFFIX:-".lustre"}	# Suffix of the generated config files

# Marker of the MD device line
export MD_MARKER=${MD_MARKER:-"MD"}

# Marker of the LVM device line
export PV_MARKER=${PV_MARKER:-"PV"}
export VG_MARKER=${VG_MARKER:-"VG"}
export LV_MARKER=${LV_MARKER:-"LV"}

declare -a CONFIG_ITEM              # Items in each line of the CSV file
declare -a NODE_NAME                # Hostnames of nodes have been configured

declare -a MGS_NODENAME             # Node names of the MGS servers
declare -a MGS_IDX                  # Indexes of MGSs in the global arrays
declare -i MGS_NUM                  # Number of MGS servers in the cluster
declare -i INIT_IDX

# All of the Lustre target items in the CSV file
declare -a HOST_NAME MODULE_OPTS DEVICE_NAME MOUNT_POINT DEVICE_TYPE FS_NAME
declare -a MGS_NIDS INDEX FORMAT_OPTIONS MKFS_OPTIONS MOUNT_OPTIONS FAILOVERS

# Heartbeat software requires that node names in the configuration directive
# must (normally) match the "uname -n" of that machine. Since the value of the
# "failover nids" field in the CSV file is the NID(s) of failover partner node,
# we have to figure out the corresponding hostname of that node.
declare -a FAILOVERS_NAMES

export VERIFY_CONNECT=true          # Verify network connectivity by default
export USE_ALLNODES=false           # Not operating on all the nodes by default
export SPECIFIED_NODELIST=""        # Specified list of nodes to be operated on
export EXCLUDED_NODELIST=""         # Specified list of nodes to be excluded
export NODES_TO_USE=""              # Defacto list of nodes to be operated on
export NODELIST_OPT=""
export VERBOSE_OUTPUT=false
export VERBOSE_OPT=""


#****************************** Common Functions ******************************#

# verbose_output string
# Output verbose information $string
verbose_output() {
    if ${VERBOSE_OUTPUT}; then
        echo "`basename $0`: $*"
    fi
    return 0
}

# error_output string
# Output error string to stderr, prefixing with ERROR
# for easy error parsing from the rest of the output.
error_output() {
    echo >&2 "$(basename $0): ERROR: $*"
    return 0
}

# error_exit rc string
# Output error to stderr via error_output and exit with rc.
error_exit() {
    local rc=$1
    shift

    error_output $*
    exit $rc
}

# Check whether the reomte command is pdsh
is_pdsh() {
    if [ "${REMOTE}" = "${REMOTE#*pdsh}" ]; then
        return 1
    fi

    return 0
}

# check_file csv_file
# Check the file $csv_file
check_file() {
    # Check argument
    if [ $# -eq 0 ]; then
        error_output "check_file(): Missing CSV file!"
        return 1
    fi

    local CSV_FILE=$1
    if [ ! -s ${CSV_FILE} ]; then
        error_output "check_file(): ${CSV_FILE}"\
                 "does not exist or is empty!"
        return 1
    fi

    return 0
}

# parse_line line
# Parse a line in the CSV file
parse_line() {
    # Check argument
    if [ $# -eq 0 ]; then
        error_output "parse_line(): Missing argument!"
        return 1
    fi

    declare -i i=0              # Index of the CONFIG_ITEM array
    declare -i length=0
    declare -i idx=0
    declare -i s_quote_flag=0   # Flag of the single quote character
    declare -i d_quote_flag=0   # Flag of the double quotes character
    local TMP_LETTER LINE

    LINE="$*"

    # Initialize the CONFIG_ITEM array
    unset CONFIG_ITEM

    # Get the length of the line
    length=${#LINE}

    i=0
    while [ ${idx} -lt ${length} ]; do
        # Get a letter from the line
        TMP_LETTER=${LINE:${idx}:1}

        case "${TMP_LETTER}" in
        ",")
            if [ ${s_quote_flag} -eq 1 -o ${d_quote_flag} -eq 1 ]
            then
                CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
            else
                i=$i+1
            fi
            idx=${idx}+1
            continue
            ;;
        "'")
            if [ ${s_quote_flag} -eq 0 ]; then
                s_quote_flag=1
            else
                s_quote_flag=0
            fi
            ;;
        "\"")
            if [ ${d_quote_flag} -eq 0 ]; then
                d_quote_flag=1
            else
                d_quote_flag=0
            fi
            ;;
        "")
            idx=${idx}+1
            continue
            ;;
        *)
            ;;
        esac
        CONFIG_ITEM[i]=${CONFIG_ITEM[i]}${TMP_LETTER}
        idx=${idx}+1
    done

    # Extract the real value of each field
    # Remove surrounded double-quotes, etc.
    for ((idx = 0; idx <= $i; idx++)); do
        # Strip the leading and trailing space-characters
        CONFIG_ITEM[idx]=`expr "${CONFIG_ITEM[idx]}" : '[[:space:]]*\(.*\)[[:space:]]*$'`

        [ -z "${CONFIG_ITEM[idx]}" ] && continue

        # Remove the surrounded double-quotes
        while [ -z "`echo "${CONFIG_ITEM[idx]}"|sed -e 's/^".*"$//'`" ]; do
            CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/^"//' -e 's/"$//'`
        done

        CONFIG_ITEM[idx]=`echo "${CONFIG_ITEM[idx]}" | sed -e 's/""/"/g'`
    done

    return 0
}

# fcanon name
# If $name is a symbolic link, then display it's value
fcanon() {
    local NAME=$1

    if [ -h "$NAME" ]; then
        readlink -f "$NAME"
    else
        echo "$NAME"
    fi
}

# configured_host host_name
#
# Check whether the devices in $host_name has been configured or not
configured_host() {
    local host_name=$1
    declare -i i

    for ((i = 0; i < ${#NODE_NAME[@]}; i++)); do
        [ "${host_name}" = "${NODE_NAME[i]}" ] && return 0
    done

    return 1
}

# remote_error fn_name host_addr ret_str
# Verify the return result from remote command
remote_error() {
    local fn_name host_addr ret_str

    fn_name=$1
    shift
    host_addr=$1
    shift
    ret_str=$*

    if [ "${ret_str}" != "${ret_str#*connect:*}" ]; then
        error_output "${fn_name}(): ${ret_str}"
        return 0
    fi

    if [ -z "${ret_str}" ]; then
        error_output "${fn_name}():" \
        "No results from remote!" \
        "Check network connectivity between the local host and ${host_addr}!"
        return 0
    fi

    return 1
}

# nid2hostname nid
# Convert $nid to hostname of the lustre cluster node
nid2hostname() {
    local nid=$1
    local host_name=
    local addr nettype ip_addr
    local ret_str

    addr=${nid%@*}
    [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp
    if [ -z "${addr}" ]; then
        echo "`basename $0`: nid2hostname() error: Invalid nid - \"${nid}\"!"
        return 1
    fi

    case "${nettype}" in
    lo*)    host_name=`hostname`;;
    elan*)  # QsNet
            # FIXME: Parse the /etc/elanhosts configuration file to
            # convert ElanID to hostname
            ;;
    ptl*)   # Portals
            # FIXME: Convert portal ID to hostname
            ;;
    *)  # tcp, o2ib, ra
        ip_addr=${addr}
        # Is it IP address or hostname?
        if [ -n "`echo ${ip_addr} | sed -e 's/\([0-9]\{1,3\}\.\)\{3,3\}[0-9]\{1,3\}//'`" ]
        then
            host_name=${ip_addr}
            echo ${host_name}
            return 0
        fi

        # Execute remote command to get the host name
        ret_str=$(${REMOTE} ${ip_addr} "hostname" 2>&1 </dev/null)
        if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
            echo "`basename $0`: nid2hostname() error:" \
            "remote command to ${ip_addr} error: ${ret_str}"
            return 1
        fi
        remote_error "nid2hostname" ${ip_addr} "${ret_str}" && return 1

        if is_pdsh; then
            host_name=`echo ${ret_str} | awk '{print $2}'`
        else
            host_name=`echo ${ret_str} | awk '{print $1}'`
        fi
        ;;
    esac

    echo ${host_name}
    return 0
}

# nids2hostname nids
# Get the hostname of the lustre cluster node which has the nids - $nids
nids2hostname() {
    local nids=$1
    local host_name=
    local nid
    local nettype

    for nid in ${nids//,/ }; do
        [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp

        case "${nettype}" in
        lo* | elan* | ptl*) ;;
        *)  # tcp, o2ib, ra
            host_name=$(nid2hostname ${nid})
            if [ ${PIPESTATUS[0]} -ne 0 ]; then
                echo "${host_name}"
                return 1
            fi
            ;;
        esac
    done

    if [ -z "${host_name}" ]; then
        echo "`basename $0`: nids2hostname() error:" \
        "Can not get the hostname from nids - \"${nids}\"!"
        return 1
    fi

    echo ${host_name}
    return 0
}

# ip2hostname_single_node nids
# Convert IP addresses in $nids into hostnames
# NID in $nids are delimited by commas, ie all the $nids belong to one node
ip2hostname_single_node() {
    local orig_nids=$1
    local nids=
    local nid host_name
    local nettype

    for nid in ${orig_nids//,/ }; do
        [ "${nid}" != "${nid#*@*}" ] && nettype=${nid#*@} || nettype=tcp

        case "${nettype}" in
        lo* | elan* | ptl*) ;;
        *)  # tcp, o2ib, ra
            host_name=$(nid2hostname ${nid})
            if [ ${PIPESTATUS[0]} -ne 0 ]; then
                echo "${host_name}"
                return 1
            fi

            nid=${host_name}@${nettype}
            ;;
        esac

        [ -z "${nids}" ] && nids=${nid} || nids=${nids},${nid}
    done

    echo ${nids}
    return 0
}

# ip2hostname_multi_node nids
# Convert IP addresses in $nids into hostnames
# NIDs belong to multiple nodes are delimited by colons in $nids
ip2hostname_multi_node() {
    local orig_nids=$1
    local nids=
    local nid

    for nid in ${orig_nids//:/ }; do
        nid=$(ip2hostname_single_node ${nid})
        if [ ${PIPESTATUS[0]} -ne 0 ]; then
            echo "${nid}"
            return 1
        fi

        [ -z "${nids}" ] && nids=${nid} || nids=${nids}:${nid}
    done

    echo ${nids}
    return 0
}

# comma_list space-delimited-list
# Convert a space-delimited list to a sorted list of unique values
# separated by commas.
comma_list() {
    # the sed converts spaces to commas, but leaves the last space
    # alone, so the line doesn't end with a comma.
    echo "$*" | tr -s " " "\n" | sort -b -u | tr "\n" " " | sed 's/ \([^$]\)/,\1/g'
}

# host_in_hostlist hostname hostlist
# Given a hostname, and a list of hostnames, return true if the hostname
# appears in the list of hostnames, or false otherwise.
host_in_hostlist() {
    local HOST=$1
    local HOSTLIST=$2

    [ -z "$HOST" -o -z "$HOSTLIST" ] && false && return

    # Hostnames in the list are separated by commas.
    [[ ,$HOSTLIST, == *,$HOST,* ]] && true && return

    false && return
}

# exclude_items_from_list list_of_items list_of_items_to_exclude
# Given a list of items, and a second list of items to exclude from
# the first list, return the contents of the first list minus the contents
# of the second.
exclude_items_from_list() {
    local INLIST=$1
    local EXCLUDELIST=$2
    local ITEM OUTLIST

    # Handle an empty inlist by throwing back an empty string.
    if [ -z "$INLIST" ]; then
        echo ""
        return 0
    fi

    # Handle an empty excludelist by throwing back the inlist unmodified.
    if [ -z "$EXCLUDELIST" ]; then
        echo $INLIST
        return 0
    fi

    for ITEM in ${INLIST//,/ }; do
        if ! host_in_hostlist $ITEM $EXCLUDELIST; then
           OUTLIST="$OUTLIST,$ITEM"
        fi
    done

    # strip leading comma
    echo ${OUTLIST#,}
}

# get_csv_nodelist csv_file
# Get the comma-separated list of all the nodes from the CSV file
get_csv_nodelist() {
    local csv_file=$1
    local all_nodelist

    # Check the CSV file
    ! check_file ${csv_file} 2>&1 && return 1

    all_nodelist=$(egrep -v "([[:space:]]|^)#" ${csv_file} | cut -d, -f 1)
    all_nodelist=$(comma_list ${all_nodelist})

    echo ${all_nodelist}
    return 0
}

# get_nodelist
# Get the comma-separated list of nodes to be operated on
# Note: CSV_FILE, USE_ALLNODES, SPECIFIED_NODELIST and EXCLUDED_NODELIST
# are global variables
get_nodelist() {
    local ALL_NODELIST

    # Get the list of all the nodes in the CSV file
    ALL_NODELIST=$(get_csv_nodelist ${CSV_FILE})
    [ ${PIPESTATUS[0]} -ne 0 ] && echo "${ALL_NODELIST}" && return 1

    if [ -z "${ALL_NODELIST}" ]; then
        echo "`basename $0`: get_nodelist() error:"\
             "There are no hosts in the ${CSV_FILE} file!"
        return 1
    fi

    if ${USE_ALLNODES}; then
        echo ${ALL_NODELIST} && return 0
    fi

    if [ -n "${SPECIFIED_NODELIST}" ]; then
        echo $(exclude_items_from_list ${SPECIFIED_NODELIST} ${EXCLUDED_NODELIST})
        return 0
    fi

    if [ -n "${EXCLUDED_NODELIST}" ]; then
        echo $(exclude_items_from_list ${ALL_NODELIST} ${EXCLUDED_NODELIST})
        return 0
    fi

    # No hosts to be operated on
    echo ""
    return 0
}

# check_nodelist nodelist
# Given a list of nodes to be operated on, check whether the nodelist is 
# empty or not and output prompt message.
check_nodelist() {
    local nodes_to_use=$1

    if [ -z "${nodes_to_use}" ]; then
        error_output "There are no nodes to be operated on."\
             "Check the node selection options (-a, -w or -x)."
        usage 1>&2
        return 1
    else
        verbose_output "Operating on the following nodes: ${nodes_to_use}"
    fi

    return 0
}

# nid_in_nidlist nid nidlist
# Given a nid, and a list of nids in one node (delimited by comma ','),
# return true if the nid appears in the list of nids, or false otherwise.
nid_in_nidlist() {
    local nid="$1"
    local nidlist="$2"
    local my_nid

    [ -z "${nid}" -o -z "${nidlist}" ] && false && return

    if [[ "${nid}" != *@* || "${nid#*@}" == tcp* ]]; then
        # network type is tcp
        for my_nid in ${nidlist//,/ }; do
            [ "${nid%@*}" = "${my_nid%@*}" ] && true && return
        done
    else
        # network type is not tcp
        [[ ,${nidlist}, == *,${nid},* ]] && true && return
    fi

    false && return
}

# get_mgs_nids mgs_hostname mgs_nids
# Get the corresponding NID(s) of the MGS node ${mgs_hostname} from the
# "mgs nids" field of one lustre target in the CSV file
get_mgs_nids() {
    local mgs_node="$1"
    local all_mgs_nids="$2"
    local mgs_nids
    local ret_str

    # Check whether the hostname of the mgs node is in 
    # the mgs nids string
    for mgs_nids in ${all_mgs_nids//:/ }; do
        if nid_in_nidlist ${mgs_node} ${mgs_nids}; then
            echo ${mgs_nids}
            return 0
        fi
    done

    # Let's use lctl to get the real nids from the mgs node
    ret_str=$($REMOTE $mgs_node "PATH=\$PATH:/sbin:/usr/sbin
$LCTL list_nids" 2>&1 </dev/null)
    if [ ${PIPESTATUS[0]} -ne 0 -a -n "${ret_str}" ]; then
        echo "$(basename $0): get_mgs_nids() error:" \
        "remote command to ${mgs_node} error: ${ret_str}"
        return 1
    fi
    remote_error "get_mgs_nids" ${mgs_node} "${ret_str}" && return 1

    local real_mgs_nids=${ret_str//${mgs_node}:/}
    for real_mgs_nid in ${real_mgs_nids}; do
        for mgs_nids in ${all_mgs_nids//:/ }; do
            if nid_in_nidlist ${real_mgs_nid} ${mgs_nids}; then
                echo ${mgs_nids}
                return 0
            fi
        done
    done

    echo "$(basename $0): get_mgs_nids() error:" \
    "Can not figure out which nids corresponding to the MGS"\
    "node ${mgs_node} from \"${all_mgs_nids}\"!"

    return 1
}

# Check the items required for OSTs, MDTs and MGS
#
# When formatting an OST, the following items: hostname,
# device name, device type and mgs nids, cannot have null value.
#
# When formatting an MDT or MGS, the following items: hostname,
# device name and device type, cannot have null value.
check_lustre_item() {
    # Check argument
    if [ $# -eq 0 ]; then
        error_output "check_lustre_item(): Missing argument"\
                  "for function check_lustre_item()!"
        return 1
    fi

    declare -i i=$1

    # Check hostname, device name and device type
    if [ -z "${HOST_NAME[i]}" ] || \
    [ -z "${DEVICE_NAME[i]}" ] || [ -z "${DEVICE_TYPE[i]}" ]; then
        error_output "check_lustre_item(): Some required"\
                  "item has null value! Check hostname,"\
                  "device name and device type!"
        return 1
    fi

    # Check mgs nids
    if [ "${DEVICE_TYPE[i]}" = "ost" ]&&[ -z "${MGS_NIDS[i]}" ]; then
        error_output "check_lustre_item(): OST's mgs nids"\
                  "item has null value!"
        return 1
    fi

    # Check mount point
    if [ -z "${MOUNT_POINT[i]}" ]; then
        error_output "check_lustre_item(): mount"\
                  "point item of target ${DEVICE_NAME[i]} has null value!"
        return 1
    fi

    return 0
}

# Get the number of MGS nodes in the cluster
get_mgs_num() {
    INIT_IDX=0
    MGS_NUM=${#MGS_NODENAME[@]}
    [ -z "${MGS_NODENAME[0]}" ] && let "INIT_IDX += 1" \
    && let "MGS_NUM += 1"
}

# is_mgs_node hostname
# Verify whether @hostname is a MGS node
is_mgs_node() {
    local host_name=$1
    declare -i i

    get_mgs_num
    for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
        [ "${MGS_NODENAME[i]}" = "${host_name}" ] && return 0
    done

    return 1
}

# Check whether the MGS nodes are in the same failover group
check_mgs_group() {
    declare -i i
    declare -i j
    declare -i idx
    local mgs_node

    get_mgs_num
    for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
        mgs_node=${MGS_NODENAME[i]}
        for ((j = ${INIT_IDX}; j < ${MGS_NUM}; j++)); do
          [ "${MGS_NODENAME[j]}" = "${mgs_node}" ] && continue 1

          idx=${MGS_IDX[j]}
          if [ "${FAILOVERS_NAMES[idx]#*$mgs_node*}" = "${FAILOVERS_NAMES[idx]}" ]
          then
            error_output "check_mgs_group():"\
            "MGS node ${mgs_node} is not in the ${HOST_NAME[idx]}"\
            "failover group!"
            return 1
          fi
        done
    done

    return 0
}

# Get and check MGS servers.
# There should be no more than one MGS specified in the entire CSV file.
check_mgs() {
    declare -i i
    declare -i j
    declare -i exp_idx    # Index of explicit MGS servers
    declare -i imp_idx    # Index of implicit MGS servers
    local is_exp_mgs is_imp_mgs
    local mgs_node

    # Initialize the MGS_NODENAME and MGS_IDX arrays
    unset MGS_NODENAME
    unset MGS_IDX

    exp_idx=1
    imp_idx=1
    for ((i = 0; i < ${#HOST_NAME[@]}; i++)); do
        is_exp_mgs=false
        is_imp_mgs=false

        # Check whether this node is an explicit MGS node 
        # or an implicit one
        if [ "${DEVICE_TYPE[i]#*mgs*}" != "${DEVICE_TYPE[i]}" ]; then
            verbose_output "Explicit MGS target" \
            "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
            is_exp_mgs=true
        fi

        if [ "${DEVICE_TYPE[i]}" = "mdt" -a -z "${MGS_NIDS[i]}" ]; then
            verbose_output "Implicit MGS target" \
            "${DEVICE_NAME[i]} in host ${HOST_NAME[i]}."
            is_imp_mgs=true
        fi

        # Get and check MGS servers
        if ${is_exp_mgs} || ${is_imp_mgs}; then
            # Check whether more than one MGS target in one MGS node
            if is_mgs_node ${HOST_NAME[i]}; then
                error_output "check_mgs():"\
                "More than one MGS target in the same node -"\
                "\"${HOST_NAME[i]}\"!"
                return 1
            fi

            # Get and check primary MGS server and backup MGS server        
            if [ "${FORMAT_OPTIONS[i]}" = "${FORMAT_OPTIONS[i]#*noformat*}" ]
            then
                # Primary MGS server
                if [ -z "${MGS_NODENAME[0]}" ]; then
                    if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
                    || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
                        error_output "check_mgs():"\
                        "There exist both explicit and implicit MGS"\
                        "targets in the CSV file!"
                        return 1
                    fi
                    MGS_NODENAME[0]=${HOST_NAME[i]}
                    MGS_IDX[0]=$i
                else
                    mgs_node=${MGS_NODENAME[0]}
                    if [ "${FAILOVERS_NAMES[i]#*$mgs_node*}" = "${FAILOVERS_NAMES[i]}" ]
                    then
                        error_output "check_mgs():"\
                        "More than one primary MGS nodes in the CSV" \
                        "file - ${MGS_NODENAME[0]} and ${HOST_NAME[i]}!"
                    else
                        error_output "check_mgs():"\
                        "MGS nodes ${MGS_NODENAME[0]} and ${HOST_NAME[i]}"\
                        "are failover pair, one of them should use"\
                        "\"--noformat\" in the format options item!"
                    fi
                    return 1
                fi
            else    # Backup MGS server
                if [ "${is_exp_mgs}" = "true" -a ${imp_idx} -gt 1 ] \
                || [ "${is_imp_mgs}" = "true" -a ${exp_idx} -gt 1 ]; then
                    error_output "check_mgs():"\
                    "There exist both explicit and implicit MGS"\
                    "targets in the CSV file!"
                    return 1
                fi

                if ${is_exp_mgs}; then # Explicit MGS
                    MGS_NODENAME[exp_idx]=${HOST_NAME[i]}
                    MGS_IDX[exp_idx]=$i
                    exp_idx=$(( exp_idx + 1 ))
                else    # Implicit MGS
                    MGS_NODENAME[imp_idx]=${HOST_NAME[i]}
                    MGS_IDX[imp_idx]=$i
                    imp_idx=$(( imp_idx + 1 ))
                fi
            fi
        fi #End of "if ${is_exp_mgs} || ${is_imp_mgs}"
    done

    # Check whether the MGS nodes are in the same failover group
    if ! check_mgs_group; then
        return 1
    fi

    return 0
}

# Execute remote command to add module options to
# the module configuration file
add_module_options() {
    declare -i i=$1
    local hostname=$2

    if [ -z "$hostname" ]; then
        error_output "add_module_options(): Missing hostname!"
        return 1
    fi

    [ -z "${MODULE_OPTS[i]}" ] && return 0

    # Execute remote command to add module options to
    # the module configuration file
    verbose_output "Adding module options to $hostname"
    $REMOTE $hostname "PATH=\$PATH:/sbin:/usr/sbin
echo \"${MODULE_OPTS[i]}\" | $MODULE_CONFIG"
    local RC=${PIPESTATUS[0]}
    if [ $RC -ne 0 ]; then
        error_output "add_module_options():"\
        "Failed to add module options to $hostname!"
        return $RC
    fi

    return 0
}

# check_lnet_connect hostname_index mgs_hostname
# Check whether the target node can contact the MGS node @mgs_hostname
# If @mgs_hostname is null, then it means the primary MGS node
check_lnet_connect() {
    declare -i i=$1
    local mgs_node=$2

    local mgs_prim_nids
    local nids_str=
    local mgs_nid 
    local ping_mgs
    local try

    # Execute remote command to check that 
    # this node can contact the MGS node
    verbose_output "Checking lnet connectivity between" \
    "${HOST_NAME[i]} and the MGS node ${mgs_node}"
    mgs_prim_nids=`echo ${MGS_NIDS[i]} | awk -F: '{print $1}'`

    if [ -z "${mgs_node}" -o $MGS_NUM -eq 1 ]; then
        nids_str=${mgs_prim_nids}    # nids of primary MGS node
        if [ -z "${nids_str}" ]; then
            error_output "check_lnet_connect():"\
            "Check the mgs nids item of host ${HOST_NAME[i]}!"\
            "Missing nids of the primary MGS node!"
            return 1
        fi
    else
        # Get the corresponding NID(s) of the MGS node ${mgs_node}
        # from the "mgs nids" field
        nids_str=$(get_mgs_nids ${mgs_node} ${MGS_NIDS[i]})
        if [ ${PIPESTATUS[0]} -ne 0 ]; then
            error_output "${nids_str}"
            return 1
        fi
    fi

    ping_mgs=false
    for mgs_nid in ${nids_str//,/ }
    do
        for try in $(seq 0 5); do
            $REMOTE ${HOST_NAME[i]} "PATH=\$PATH:/sbin:/usr/sbin
$LCTL ping $mgs_nid 5 1>/dev/null"
            if [ ${PIPESTATUS[0]} -eq 0 ]; then
                # This node can contact the MGS node
                verbose_output "${HOST_NAME[i]} can contact the MGS" \
                "node $mgs_node by using nid \"$mgs_nid\"!"
                ping_mgs=true
                break
            fi
        done
    done

    if ! ${ping_mgs}; then
        error_output "check_lnet_connect():" \
        "${HOST_NAME[i]} cannot contact the MGS node ${mgs_node}"\
        "with nids - \"${nids_str}\"! Check ${LCTL} command!"
        return 1
    fi

    return 0
}

# Start lnet network in the cluster node and check that 
# this node can contact the MGS node
check_lnet() {
    if ! $VERIFY_CONNECT; then
        return 0
    fi

    # Check argument
    if [ $# -eq 0 ]; then
        error_output "check_lnet(): Missing argument!"
        return 1
    fi

    declare -i i=$1
    declare -i j
    local ret_str

    # Execute remote command to start lnet network
    verbose_output "Starting lnet network on ${HOST_NAME[i]}"
    ret_str=$($REMOTE ${HOST_NAME[i]} "PATH=\$PATH:/sbin:/usr/sbin
modprobe lnet && $LCTL network up" 2>&1)
    if [ ${PIPESTATUS[0]} -ne 0 ]; then
        error_output "check_lnet(): start lnet network on" \
        "${HOST_NAME[i]} error: $ret_str"
        return 1
    fi

    if is_mgs_node ${HOST_NAME[i]}; then
        return 0
    fi

    # Execute remote command to check that 
    # this node can contact the MGS node
    for ((j = 0; j < ${MGS_NUM}; j++)); do
        if ! check_lnet_connect $i ${MGS_NODENAME[j]}; then
            return 1
        fi
    done

    return 0
}

# Start lnet network in the MGS node
start_mgs_lnet() {
    declare -i i
    declare -i idx

    if [ -z "${MGS_NODENAME[0]}" -a  -z "${MGS_NODENAME[1]}" ]; then
        if ${USE_ALLNODES}; then
            verbose_output "There is no MGS target in the ${CSV_FILE} file."
        else
            verbose_output "There is no MGS target in the node list \"${NODES_TO_USE}\"."
        fi
        return 0
    fi

    for ((i = ${INIT_IDX}; i < ${MGS_NUM}; i++)); do
        # Execute remote command to add lnet options lines to 
        # the MGS node's modprobe.conf/modules.conf
        idx=${MGS_IDX[i]}
        add_module_options $idx ${MGS_NODENAME[i]} || return ${PIPESTATUS[0]}

        # Start lnet network in the MGS node
        check_lnet $idx || return ${PIPESTATUS[0]}
    done

    return 0
}

# Get all the Lustre target items in the CSV file and do some checks.
get_lustre_items() {
    # Check argument
    if [ $# -eq 0 ]; then
        error_output "get_lustre_items(): Missing argument"\
                  "for function get_lustre_items()!"
        return 1
    fi

    local CSV_FILE=$1
    local LINE
    local marker
    local hostname
    declare -i line_num=0
    declare -i idx=0

    exec 9< ${CSV_FILE}
    while read -u 9 -r LINE; do
        line_num=${line_num}+1
        # verbose_output "Parsing line ${line_num}: $LINE"

        # Get rid of the empty line
        [ -z "`echo ${LINE} | awk '/[[:alnum:]]/ {print $0}'`" ] && continue

        # Get rid of the comment line
        [ -z "`echo \"${LINE}\" | egrep -v \"([[:space:]]|^)#\"`" ] && continue

        # Skip the Linux MD/LVM line
        marker=$(echo ${LINE} | cut -d, -f 2)
        if [ "${marker}" = "${MD_MARKER}" -o "${marker}" = "${PV_MARKER}" ] \
        || [ "${marker}" = "${VG_MARKER}" -o "${marker}" = "${LV_MARKER}" ]; then
            continue
        fi

        # Skip the host which is not specified in the host list
        if ! ${USE_ALLNODES}; then
            hostname=$(echo ${LINE} | cut -d, -f 1)
            ! host_in_hostlist ${hostname} ${NODES_TO_USE} && continue
        fi

        # Parse the config line into CONFIG_ITEM
        if ! parse_line "$LINE"; then
            error_output "parse_line(): Occurred"\
                  "on line ${line_num} in ${CSV_FILE}: $LINE"
            return 1
        fi

        HOST_NAME[idx]=${CONFIG_ITEM[0]}
        MODULE_OPTS[idx]=${CONFIG_ITEM[1]}
        DEVICE_NAME[idx]=${CONFIG_ITEM[2]}
        MOUNT_POINT[idx]=${CONFIG_ITEM[3]}
        DEVICE_TYPE[idx]=${CONFIG_ITEM[4]}
        FS_NAME[idx]=${CONFIG_ITEM[5]}
        MGS_NIDS[idx]=${CONFIG_ITEM[6]}
        INDEX[idx]=${CONFIG_ITEM[7]}
        FORMAT_OPTIONS[idx]=${CONFIG_ITEM[8]}
        MKFS_OPTIONS[idx]=${CONFIG_ITEM[9]}
        MOUNT_OPTIONS[idx]=${CONFIG_ITEM[10]}
        FAILOVERS[idx]=${CONFIG_ITEM[11]}

        MODULE_OPTS[idx]=`echo "${MODULE_OPTS[idx]}" | sed 's/"/\\\"/g'`

        # Convert IP addresses in NIDs to hostnames
        FAILOVERS_NAMES[idx]=$(ip2hostname_multi_node ${FAILOVERS[idx]})
        if [ ${PIPESTATUS[0]} -ne 0 ]; then
            error_output "${FAILOVERS_NAMES[idx]}"
            return 1
        fi

        # Check some required items for formatting target
        if ! check_lustre_item $idx; then
            error_output "check_lustre_item():"\
                  "Occurred on line ${line_num} in ${CSV_FILE}."
            return 1    
        fi

        idx=${idx}+1
    done

    return 0
}
